msg_tool\scripts\kirikiri\archive\xp3/
read.rs1use super::archive::*;
2use super::consts::*;
3use super::crypt::*;
4use crate::ext::io::*;
5use crate::types::*;
6use anyhow::Result;
7use std::io::{Read, Seek, SeekFrom};
8use std::sync::{Arc, Mutex};
9
10impl<'a> Xp3Archive<'a> {
11 pub fn new<T: Read + Seek + Send + Sync + std::fmt::Debug + 'a>(
12 stream: T,
13 config: &ExtraConfig,
14 filename: &str,
15 base_offset: u64,
16 ) -> Result<Self> {
17 #[allow(unused_mut)]
18 let mut crypt: Box<dyn Crypt + Send + Sync> =
19 if let Some(game_title) = &config.xp3_game_title {
20 query_crypt_schema(game_title)
21 .ok_or_else(|| {
22 anyhow::anyhow!("Unsupported game title for XP3 archive: {}", game_title)
23 })?
24 .create_crypt(filename, config)?
25 } else {
26 Box::new(NoCrypt::new())
27 };
28 let mut stream = Box::new(stream);
29 if base_offset != 0 {
30 stream.seek(SeekFrom::Start(base_offset))?;
31 }
32 stream
33 .read_and_equal(XP3_MAGIC)
34 .map_err(|e| anyhow::anyhow!("Invalid xp3 signature: {}", e))?;
35 let mut index_offset = stream.read_u64()?;
36 let mut minor_version = 0;
37 if index_offset == TVP_XP3_CURRENT_HEADER_VERSION {
38 minor_version = stream.read_u32()?;
39 let sig = stream.read_u8()?;
40 if sig != TVP_XP3_INDEX_CONTINUE {
41 anyhow::bail!("Unsupported XP3 index format: {} is not continue flag", sig);
42 }
43 let index_offset_offset = stream.read_i64()?;
44 if index_offset_offset != 0 {
45 stream.seek_relative(index_offset_offset)?;
46 }
47 index_offset = stream.read_u64()?;
48 }
49 index_offset += base_offset;
50 stream.seek(SeekFrom::Start(index_offset))?;
51 let mut entries = Vec::new();
52 let mut extras = Vec::new();
53 {
54 let mut index_stream = Self::get_index_stream(&mut stream)?;
55 let mut sig = [0u8; 4];
56 loop {
57 let readed = index_stream.read_most(&mut sig)?;
58 if readed == 0 {
59 break;
60 }
61 if readed < 4 {
62 anyhow::bail!("Invalid chunk signature in index");
63 }
64 let mut size = index_stream.read_u64()?;
65 if &sig == CHUNK_FILE {
66 let mut name = None;
67 let mut flags = None;
68 let mut file_hash = None;
69 let mut original_size = None;
70 let mut archived_size = None;
71 let mut timestamp = None;
72 let mut segments = Vec::new();
73 let mut seg_offset = 0;
74 let mut entry_extras = Vec::new();
75 while size > 0 {
76 if size < 12 {
77 anyhow::bail!("Invalid chunk size in index");
78 }
79 let mut chunk_sig = [0u8; 4];
80 index_stream.read_exact(&mut chunk_sig)?;
81 let mut chunk_size = index_stream.read_u64()?;
82 size -= 12;
83 if size < chunk_size {
84 anyhow::bail!("Invalid chunk size in index");
85 }
86 size -= chunk_size;
87 if &chunk_sig == CHUNK_INFO {
88 if chunk_size < 20 {
89 anyhow::bail!("Invalid info chunk size in index");
90 }
91 flags = Some(index_stream.read_u32()?);
92 original_size = Some(index_stream.read_u64()?);
93 archived_size = Some(index_stream.read_u64()?);
94 chunk_size -= 20;
95 let (n, s) = crypt.read_name(&mut index_stream)?;
96 name = Some(n);
97 chunk_size -= s;
98 } else if &chunk_sig == CHUNK_ADLR {
99 if chunk_size == 4 {
100 file_hash = Some(index_stream.read_u32()?);
101 chunk_size -= 4;
102 }
103 } else if &chunk_sig == CHUNK_SEGM {
104 while chunk_size > 0 {
105 if chunk_size < 0x1C {
106 anyhow::bail!("Invalid segm chunk size in index");
107 }
108 let seg_flags = index_stream.read_u32()?;
109 let start = index_stream.read_u64()?;
110 let original_size = index_stream.read_u64()?;
111 let archived_size = index_stream.read_u64()?;
112 chunk_size -= 0x1C;
113 segments.push(Segment {
114 is_compressed: seg_flags != 0,
115 start,
116 offset_in_file: seg_offset,
117 original_size,
118 archived_size,
119 });
120 seg_offset += original_size;
121 }
122 } else if &chunk_sig == CHUNK_TIME {
123 if chunk_size == 8 {
124 timestamp = Some(index_stream.read_u64()?);
125 chunk_size -= 8;
126 }
127 } else {
128 let data = index_stream.read_exact_vec(chunk_size as usize)?;
129 chunk_size = 0;
130 entry_extras.push(ExtraProp {
131 tag: chunk_sig.into(),
132 data,
133 });
134 }
135 if chunk_size > 0 {
136 index_stream.skip(chunk_size)?;
137 }
138 }
139 let mut entry = Xp3Entry {
140 name: name
141 .ok_or_else(|| anyhow::anyhow!("Missing name chunk in file entry"))?,
142 flags: flags
143 .ok_or_else(|| anyhow::anyhow!("Missing flags chunk in file entry"))?,
144 file_hash: file_hash.unwrap_or(0),
145 original_size: original_size.ok_or_else(|| {
146 anyhow::anyhow!("Missing original size chunk in file entry")
147 })?,
148 archived_size: archived_size.ok_or_else(|| {
149 anyhow::anyhow!("Missing archived size chunk in file entry")
150 })?,
151 timestamp,
152 segments,
153 extras: entry_extras,
154 extra: None,
155 };
156 if entry.name == "startup.tjs"
157 && entry.flags != 0
158 && crypt.startup_tjs_not_encrypted()
159 {
160 entry.flags = 0;
161 }
162 entries.push(entry);
163 } else {
164 let data = index_stream.read_exact_vec(size as usize)?;
165 let tag = sig.into();
166 if config.xp3_game_title.is_none() && tag == "Hxv4" {
167 match Hxv4Crypt::new(filename, config) {
168 Ok(c) => {
169 crypt = Box::new(c);
170 }
171 Err(e) => {
172 eprintln!("WARNING: Failed to load filelist.json: {}", e);
173 crate::COUNTER.inc_warning();
174 }
175 }
176 }
177 extras.push(ExtraProp { tag, data });
178 }
179 }
180 }
181 let crypt = Arc::new(crypt);
182 let mut archive = Self {
183 inner: Arc::new(Mutex::new(stream)),
184 crypt: crypt.clone(),
185 base_offset,
186 index_offset,
187 minor_version,
188 entries,
189 extras,
190 };
191 crypt.init(&mut archive)?;
192 Ok(archive)
193 }
194
195 fn get_index_stream<'c, 'b, T: Read + Seek + std::fmt::Debug + 'b>(
196 stream: &'c mut Box<T>,
197 ) -> Result<Box<dyn Read + 'c>> {
198 let index_type = stream.read_u8()?;
199 Ok(match index_type {
200 TVP_XP3_INDEX_ENCODE_RAW => {
201 let index_size = stream.read_u64()?;
202 Box::new(StreamRegion::with_size(stream, index_size)?)
203 }
204 TVP_XP3_INDEX_ENCODE_ZLIB => {
205 let packed_size = stream.read_u64()?;
206 let _original_size = stream.read_u64()?;
207 let mut compressed_data = StreamRegion::with_size(stream, packed_size)?;
208 if compressed_data.peek_and_equal(ZSTD_SIGNATURE).is_ok() {
209 Box::new(zstd::stream::read::Decoder::new(compressed_data)?)
210 } else {
211 Box::new(flate2::read::ZlibDecoder::new(compressed_data))
212 }
213 }
214 _ => {
215 anyhow::bail!("Unsupported index type: {}", index_type);
216 }
217 })
218 }
219}